﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace WgFrame.Net
{
    public enum ClientState
    {
        PeerCreated,
        Connecting,
        Connected,
        Disconnecting,
        Disconnected,
    }


    public class WgClient : IWgPeerListener
    {
        public WgPeer Peer { get; private set; }

        public ClientState State { get; set; }

        public ConnectionCallbacksContainer ConnectionCallbackTargets;

        public string ServerAddress { get; private set; }

        public string AppId { get; private set; }

        public event Action<OperationResponse> OpResponseReceived;

        private class CallbackTargetChange
        {
            public readonly object Target;
            public readonly bool AddTarget;

            public CallbackTargetChange(object target, bool addTarget)
            {
                Target = target;
                AddTarget = addTarget;
            }
        }

        private readonly Queue<CallbackTargetChange> callbackTargetChanges = new Queue<CallbackTargetChange>();

        public WgClient()
        {
            ConnectionCallbackTargets = new ConnectionCallbacksContainer(this);
            Peer = new WgPeer(this);
            State = ClientState.PeerCreated;
        }

        public bool Connect(string serverAddress, string appId, object custom)
        {
            if (Peer.PeerState != PeerStateValue.Disconnected)
            {
                Debug.LogWarning("Can only connect while in state 'Disconnected'. Current state: " + Peer.PeerState);
                return false;
            }

            ServerAddress = serverAddress;
            AppId = appId;
            if (Peer.Connect(ServerAddress, AppId, custom))
            {
                State = ClientState.Connecting;
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Disconnect()
        {
            if (State != ClientState.Disconnected)
            {
                State = ClientState.Disconnecting;
                Peer.Disconnect();
            }
        }

        public void Service()
        {
            if (Peer != null)
            {
                Peer.Service();
            }
        }

        public void DebugReturn(DebugLevel level, string message)
        {
            if (Peer.DebugOut != DebugLevel.ALL && level > Peer.DebugOut)
            {
                return;
            }
            if (level == DebugLevel.ERROR)
            {
                Debug.LogError(message);
            }
            else if (level == DebugLevel.WARNING)
            {
                Debug.LogWarning(message);
            }
            else if (level == DebugLevel.INFO)
            {
                Debug.Log(message);
            }
            else if (level == DebugLevel.ALL)
            {
                Debug.Log(message);
            }
        }

        public void OnOperationResponse(OperationResponse operationResponse)
        {
            ConnectionCallbackTargets.OpResponseReceived(operationResponse);
            if (OpResponseReceived != null) 
            {
                OpResponseReceived(operationResponse);
            }
        }

        public void OnStatusChanged(StatusCode statusCode)
        {
            DebugReturn(DebugLevel.ALL, "OnStatusChanged: " + statusCode);
            switch (statusCode)
            {
                case StatusCode.Connect:
                    {
                        Peer.EstablishEncryption();
                    }
                    break;
                case StatusCode.Disconnect:
                    {
                        switch (State)
                        {
                            case ClientState.PeerCreated:
                            case ClientState.Disconnecting:
                                State = ClientState.Disconnected;
                                ConnectionCallbackTargets.OnDisconnected();
                                break;
                            case ClientState.Disconnected:
                                break;
                            default:
                                {
                                    DebugReturn(DebugLevel.WARNING, "Got a unexpected Disconnect in Client State: " + State);
                                    State = ClientState.Disconnected;
                                    ConnectionCallbackTargets.OnDisconnected();
                                }
                                break;
                        }
                    }
                    break;
                case StatusCode.ExceptionOnConnect:
                    break;
                case StatusCode.SendError:
                case StatusCode.ExceptionOnReceive:
                case StatusCode.TimeoutDisconnect:
                    State = ClientState.Disconnecting;
                    break;
                case StatusCode.EncryptionEstablished:
                    {
                        State = ClientState.Connected;
                        ConnectionCallbackTargets.OnConnected();
                    }
                    break;
                case StatusCode.EncryptionFailedToEstablish:
                    break;
            }
        }

        public void AddCallbackTarget(object target)
        {
            callbackTargetChanges.Enqueue(new CallbackTargetChange(target, true));
        }

        public void RemoveCallbackTarget(object target)
        {
            callbackTargetChanges.Enqueue(new CallbackTargetChange(target, false));
        }

        protected internal void UpdateCallbackTargets()
        {
            while (callbackTargetChanges.Count > 0)
            {
                CallbackTargetChange change = callbackTargetChanges.Dequeue();

                UpdateCallbackTarget(change, ConnectionCallbackTargets);
            }
        }

        private void UpdateCallbackTarget<T>(CallbackTargetChange change, List<T> container) where T : class
        {
            T target = change.Target as T;
            if (target != null)
            {
                if (change.AddTarget)
                {
                    container.Add(target);
                }
                else
                {
                    container.Remove(target);
                }
            }
        }
    }
}


